#include <QCoreApplication>
#include <QFileInfo>
#include <QMessageBox>
#include <QDomDocument>
#include <QXmlStreamReader>
#include <QTextStream>
#include <QProcess>

#include "monieresdict.h"
#include "transliterator.h"
#include "teiheader.h"

MonierESDict::MonierESDict()
{
    transliterator = new Transliterator();

    QString xmlfile = QCoreApplication::applicationDirPath()+"/dict/mwe/mwe.xml";


    //load external idx, since monier.xml maybe updated
    //if monier.xml is not match the shipped monier.idx,
    //inform user to update the idx.
    QString idxfile = QCoreApplication::applicationDirPath()+"/dict/mwe/mwe.idx";


    //we put all this files in resource.qrc

    QFileInfo idxinfo(idxfile);
    QFileInfo xmlinfo(xmlfile);
    if(!xmlinfo.exists()) {
        QMessageBox msgBox;
        msgBox.setText(QObject::tr("The \"mwe.xml\" file not exists.\n"
                          "Please download it first.\n"));
        msgBox.exec();
        exit(0);
    }

    if(!xmlinfo.isReadable()) {
        QMessageBox msgBox;
        msgBox.setText(QObject::tr("You had no read permission on \"mwe.xml\"\n"
                          "Please correct it.\n"));
        msgBox.exec();
        exit(0);
    }

    //printf("%lld", xmlinfo.size());
    if(!idxinfo.exists()) {
        //the external idx is not exists.
        //let check the xmlfile.
        //this is the monier.xml file size which we use.
        qint64 xmlfilesize = 11246605;

        //mwe.xml updated. warn user to re-generate the idx file.
        if(xmlinfo.size() != xmlfilesize){
            QMessageBox msgBox;
            msgBox.setText(QObject::tr("The mwe.xml has been updated.\n"
                           "Press 'OK' to run Index Generator to generate the idx file\n"
                           "And put the 'mwe.idx' into the dir same as mwe.xml\n\n"
                           "Otherwise, the App can not work as expected!!!"));
            msgBox.exec();

            QString gencmd = QCoreApplication::applicationDirPath() + "/genmonieridx2";
            QProcess::startDetached(gencmd);

            exit(0);
        } else //check passed, we use internal idx.*/
            idxfile = ":/indexes/mwe.idx";
    }

    //QString scanfile = ":/indexes/monierse-scanjpg.idx";

    idx = createMonierESIndex(idxfile);
    /*ref = createMonierSERef(idxfile);
    scanidx = createMonierSEScanIdx(scanfile);
    greekidx = createMonierSEGreekIdx(greekfile);
    wnaidx = createWorksAndAuthorsIdx(wnafile);*/

    mweXml = new QFile(xmlfile);
    //here need check succ or NOT.
    mweXml->open(QIODevice::ReadOnly | QIODevice::Text);

    //this->keyword = "";
}

MonierESDict::~MonierESDict()
{
    mweXml->close();
}

DictionaryDescription MonierESDict::getDescription()
{
    QString acknowledgefile = QCoreApplication::applicationDirPath()+"/dict/mwe/mweheader.xml";

    TeiHeader *teih = new TeiHeader(acknowledgefile);

    QString teiinfo;
    teiinfo += QString("<html><body style=\"font-size:10pt;\"><p>This infomation is generated from '%1'' automatically, "
                    "for more informations, please refer to '%1' and related website below.</p>").arg(QFileInfo(acknowledgefile).fileName());

    teiinfo += teih->toHtml();

    teiinfo += "<br><b>Related website:</b><br>"
            "<a href=\"http://www.sanskrit-lexicon.uni-koeln.de\">"
            "Cologne Digital Sanskrit Dictionaries and Online version</a>";

    teiinfo += "</body></html>";
    DictionaryDescription d;
    d.setName("Monier Wiliams English-Sanskrit Dictionary");
    d.setShortName("MW Eng-Sans");
    d.setIcon(QIcon(":/mwicon.png"));
    d.setAbout(teiinfo);
    d.setFromLang(DictionaryDescription::English);
    d.setToLang(DictionaryDescription::Sanskrit);
    d.setVersion("1.0");
    return d;
}

MonierESIndex MonierESDict::createMonierESIndex(QString idxfile)
{
    MonierESIndex index;
    QFile file(idxfile);
    if (!file.open(QIODevice::ReadOnly | QIODevice::Text))
        return index;
    while (!file.atEnd()) {
#if defined(Q_OS_LINUX)
        qApp->processEvents();
#endif
        QByteArray line = file.readLine();
        QStringList linelist = QString(line).split(";");
        QString key = linelist[0];

        //also setup allkey String List.
        //if(this->allkey.indexOf(key) == -1)
        this->allkey.append(key);

        qint64 pos = linelist[1].toLongLong();
        int offset = linelist[2].toUInt();
        MonierESPosition p;
        p.first = pos;
        p.second = offset;
        index.insert(key, p);
    }
    file.close();
    return index;
}

MonierESItemList MonierESDict::findMonierESIndex(QString key)
{
    MonierESItemList list;
    MonierESIndexIterator i = idx.find(key);
    while (i != idx.end() && i.key() == key) {
        list.push_back(*i);
        ++i;
    }
    qSort(list.begin(), list.end());
    return list;
}

static bool shouldRemove(QString first, QString second)
{
    return (first == second);
}
/*
QStringList MonierESDict::getCandidateWords(QString key)
{

    keyword = key;

    QStringList nextList;

    QString word = key;

    //your input maybe not in allkey, since allkey only store the keywords of dict
    //and your input may not be a keyword.
    //so this function may return a Empty QStringList.

    int id = allkey.indexOf(key);

    if(id != -1) {
        nextList = allkey.mid(id,100);
        qSort(nextList.begin(), nextList.end());
        //remove duplicated items in nextList.
        QStringList::iterator it = std::unique (nextList.begin(), nextList.end(), shouldRemove);
        nextList.erase(it, nextList.end());
        return nextList;
    }

    //we will try to find first record startsWith key.
    //The key is a english words, so it should be not so slow.
    for(int i = 0; i < allkey.size(); i++) {
        if(allkey.at(i).startsWith(key)) {
            id = i;
            break;
        }
    }

    if(id != -1) {
        nextList = allkey.mid(id,100);
        qSort(nextList.begin(), nextList.end());
        //remove duplicated items in nextList.
        QStringList::iterator it = std::unique (nextList.begin(), nextList.end(), shouldRemove);
        nextList.erase(it, nextList.end());
        return nextList;
    }
    //we will never guess what you input, and just return a Empty StringList.
    return nextList;
}
*/

QList<QPair<QString, QString> > MonierESDict::getCandidateWords(QString key)
{

    //keyword = key;

    QList<QPair<QString, QString> > wordsHash;

    //your input maybe not in allkey, since allkey only store the keywords of dict
    //and your input may not be a keyword.
    //so this function may return a Empty QStringList.

    int id = allkey.indexOf(key);

    QStringList nextList;

    if(id != -1) {
        nextList = allkey.mid(id,100);
        qSort(nextList.begin()+1, nextList.end());
        //remove duplicated items in nextList.
        QStringList::iterator it = std::unique (nextList.begin(), nextList.end(), shouldRemove);
        nextList.erase(it, nextList.end());
        for(int i = 0; i < nextList.size(); i++)
            wordsHash.append(QPair<QString, QString>(nextList.at(i), nextList.at(i)));
        return wordsHash;
    }

    //we will try to find first record startsWith key.
    //The key is a english words, so it should be not so slow.
    for(int i = 0; i < allkey.size(); i++) {
        if(allkey.at(i).startsWith(key)) {
            id = i;
            break;
        }
    }

    if(id != -1) {
        nextList = allkey.mid(id,100);
        qSort(nextList.begin()+1, nextList.end());
        //remove duplicated items in nextList.
        QStringList::iterator it = std::unique (nextList.begin(), nextList.end(), shouldRemove);
        nextList.erase(it, nextList.end());
        for(int i = 0; i < nextList.size(); i++)
            wordsHash.append(QPair<QString, QString>(nextList.at(i), nextList.at(i)));
        return wordsHash;
    }

    //we will never guess what you input, and just return a Empty Hash.
    return wordsHash;
}


QString MonierESDict::readMonierESContent(qint64 pos, int offset)
{
    mweXml->seek(pos);
    return mweXml->read(offset);
}


QStringList MonierESDict::findWords(QString key)
{
    QStringList ret;
    MonierESItemList list = findMonierESIndex(key);
    for (int i = 0; i < list.size(); ++i) {
        MonierESPosition p = list.at(i);
        qint64 pos = p.first;
        int offset = p.second;
        //printf("%lld;%d;\n", pos,offset);
        QString content = readMonierESContent(pos, offset);
        ret<<content;
        //printf("%s\n", content.toStdString().c_str());
    }
    return ret;
}


void MonierESDict::queryWord(QString key)
{

    //keyword = key;

    QStringList wordrecords = findWords(key);
    QString result = "";
    if(wordrecords.size() == 0) {
        emit readyToShow(QString());
        return;
    }
    result +="<html><body style=\"font-size:10pt;\"><meta name=\"qrichtext\" content=\"1\"/>"
            "<span style=\"font-size: 12pt; color:#0000ff;\">"
            +key + "</span><br>";
    for(int i = 0; i < wordrecords.size(); i++)
        result += "<br>" + processLine(wordrecords.at(i)) + "<br>";

    result +="</body></html>";
    emit readyToShow(result);
    return;
}

QString MonierESDict::markup_body(QString string)
{
    QString result_string = "";
    QString sanskrit_string = "";

    bool in_sanskrit_context = false;

    //as0/asp0 had been substitute with <s>
    bool in_as0_drop_context = false;

    QXmlStreamReader xml(string);
    while (!xml.atEnd() && !xml.hasError())
    {
        xml.readNext();

        QString name = xml.name().toString();
        QString text = xml.text().toString();

        //unprocessed element.
        if(xml.isStartElement() && name == "abE")
            continue;

        if(xml.isStartElement() && (name == "shortlong" || name == "shc"))
            continue;

        if(xml.isStartElement() && name == "hom")
            continue;

        if( name == "as0" ||name == "asp0" ) {
            if(xml.isStartElement()) {
                in_as0_drop_context = true;
                continue;
            }
            else
                in_as0_drop_context = false;
        }

        if(in_as0_drop_context)
            continue;

        //sanskrit context need proccessed specially.
        if(name == "s") {
            if(xml.isStartElement()) {
                in_sanskrit_context = true;
            }
            else {
                in_sanskrit_context = false;

                //process the OM, it is very special.
                if(sanskrit_string == "om")
                    sanskrit_string = "OM";

                sanskrit_string.replace("/","");

                //trans here.
                QString itrans_string = transliterator->slp1_to_itrans(sanskrit_string);
                sanskrit_string = transliterator->itrans_to_deva(itrans_string);
                sanskrit_string += "<span style=\"font-size: 9pt; color:#55557f;\"> /" + transliterator->itrans_to_iast(itrans_string.toUtf8()) + "/</span>";

                //post fix
                sanskrit_string.replace(QString::fromUtf8("√"), QString::fromUtf8("<span style=\"color:#5500ff;\">√</span>"));
                sanskrit_string.replace(QString::fromUtf8("*"), QString::fromUtf8("<span style=\"color:#ff0000;\">*</span>"));
                sanskrit_string.replace(QString::fromUtf8("°"), QString::fromUtf8("<span style=\"color:#ff0000;\">°</span>"));

                result_string += "<span style=\"font-family:'Sanskrit 2003'; font-size: 12pt;color:#00aa00;\">" + sanskrit_string + "</span>";
                sanskrit_string = "";
            }
        }

        //sanskrit context
        //save text to sanskrit-string.
        if(in_sanskrit_context) {
            if(name == "b" || name == "b1"){
                if(xml.isStartElement())
                    sanskrit_string += "[";
                else
                    sanskrit_string += "]";
            }
            if(name == "p" || name == "p1"){
                if(xml.isStartElement())
                    sanskrit_string += "(";
                else
                    sanskrit_string += ")";
            }
            if(xml.isStartElement() && name == "root") {
                sanskrit_string += QString::fromUtf8("√");
            }
            if(xml.isStartElement() && (name == "srs" || name =="srs1" )) {
                sanskrit_string += QString::fromUtf8("*");
            }

            if(xml.isStartElement() && (name == "sr" || name =="sr1" || name == "fcom")) {
                sanskrit_string += QString::fromUtf8("°");
            }
            sanskrit_string += text;
        }

        //non-sanskrit context.
        //save it directly to result_string.
        if(!in_sanskrit_context) {

            if(name == "b" || name == "b1"){
                if(xml.isStartElement())
                    result_string += "[";
                else
                    result_string += "]";
            }
            if(name == "p" || name == "p1"){
                if(xml.isStartElement())
                    result_string += "(";
                else
                    result_string += ")";
            }
            if(name == "quote"){
                if(xml.isStartElement())
                    result_string += "\"";
                else
                    result_string += "\"";
            }
            if(xml.isStartElement() && name == "root") {
                result_string += QString::fromUtf8("√");
            }
            if(xml.isStartElement() && name == "amp") {
                result_string += QString::fromUtf8("&");
            }
            if(xml.isStartElement() && name == "eq") {
                result_string += QString::fromUtf8("=");
            }
            if(xml.isStartElement() && name == "auml") {
                result_string += QString::fromUtf8("ä");
            }
            if(xml.isStartElement() && name == "euml") {
                result_string += QString::fromUtf8("ё");
            }
            if(xml.isStartElement() && name == "ouml") {
                result_string += QString::fromUtf8("ö");
            }
            if(xml.isStartElement() && name == "uuml") {
                result_string += QString::fromUtf8("ü");
            }

            if(xml.isStartElement() && name == "fs") {
                result_string += QString::fromUtf8("/");
            }
            if(xml.isStartElement() && name == "msc") {
                result_string += QString::fromUtf8(";");
            }
            if(xml.isStartElement() && (name == "etc" || name == "etc1" || name == "etcetc")) {
                result_string += QString::fromUtf8(" etc. ");
            }

            if(xml.isStartElement() && (name == "srs" || name =="srs1" )) {
                result_string += QString::fromUtf8("*");
            }

            if(xml.isStartElement() && (name == "sr" || name =="sr1" || name == "fcom")) {
                result_string += QString::fromUtf8("°");
            }
            if(xml.isStartElement() && name == "see") {
                //result_string += QString::fromUtf8("»");
                result_string += "<span style=\"font-style:italic;\">" + QString::fromUtf8("»") + "refer to</span>";
            }

            if(xml.isStartElement() && name == "cf") {
                result_string += QString::fromUtf8("<span style=\"font-style:italic;\">c.f.</span>");
            }
            if(xml.isStartElement() && name == "qv") {
                result_string += QString::fromUtf8("<span style=\"font-style:italic;\">q.v.</span>");
            }
            //should keep same style as sanskrit.
            if(name == "ns") {
                if(xml.isStartElement())
                    result_string += "<span style=\"color:#00aa7f;\">";
                else
                    result_string += "</span>";
            }
            if(name == "i") {
                if(xml.isStartElement())
                    result_string += "<span style=\"font-style: italic; color:#FF00BF\">";
                else
                    result_string += "</span>";
            }

          /*  if(name == "lb") {
                result_string += "<br>";
            } */
            if(name == "bio" || name == "bot"){
                if(xml.isStartElement())
                    result_string += "<span style=\"font-size:8pt; color:#B40431;\">";
                else
                    result_string += "</span>";
            }

            if(name == "ab") {
                if(xml.isStartElement())
                    result_string += "<span style=\" color:#55aa00;\">";
                else
                    result_string += "</span>";
            }

            //pc means the meaning of this word sperated in two pages.
            //we ignore here.
            if(name == "pc") {
                if(xml.isStartElement()) {
                    xml.readNext();
                    continue;
                }
            }

            if(name == "etym") {
                if(xml.isStartElement())
                    result_string += "<span style=\"font-style:italic; color:#ff5500;\">";
                else
                    result_string += "</span>";
            }

            if(name == "lex" || name == "vlex") {
                if(xml.isStartElement())
                    result_string += "<span style=\"font-style:italic; color:#FF00BF\">";
                else
                    result_string += "</span>";
            }

            result_string += text;
        }
    }


    //final fix.

    result_string.replace("_"," ");
    result_string.replace("~"," ");


    //post fix
    result_string.replace(QString::fromUtf8("√"), QString::fromUtf8("<span style=\"color:#5500ff;\">√</span>"));
    result_string.replace(QString::fromUtf8("*"), QString::fromUtf8("<span style=\"color:#ff0000;\">*</span>"));
    result_string.replace(QString::fromUtf8("°"), QString::fromUtf8("<span style=\"color:#ff0000;\">°</span>"));
    return result_string;
}

//process a record and return the marked up result_string.
QString MonierESDict::processLine(QString line)
{

    QString result_string = "";

    //header, we only need key2 and hom.
    QString key2_string = "";
    QString hom_string = "";
    //body,
    QString body_string = "";
    //end, we only need to show page/column.
    //lnum had been processed to a idx with monier.xml
    //And lnum only used as a reference in body.
    QString pc_string = "";
    QString lnum_string = "";


    QDomDocument doc("mydocument");
    doc.setContent(line);
    QDomElement docElem = doc.documentElement();

    QDomNode n = docElem.firstChild();
    while(!n.isNull()) {
        QDomElement e = n.toElement(); // try to convert the node to an element.
        if(!e.isNull()) {
            //header
            if(e.tagName() == "h") {
                QDomNode key2 = n.namedItem("key2");
                QDomNode hom = n.namedItem("hom");
                if(!key2.isNull()) {
                    QTextStream stream(&key2_string);
                    key2.save(stream, 0);

                }
                if(!hom.isNull()){
                    hom_string = hom.toElement().text();
                }
            }
            //body
            if(e.tagName() == "body") {
                QTextStream stream(&body_string);
                n.save(stream,0);
                body_string=body_string.simplified();
                result_string += markup_body(body_string);
            }
            //tail just ignored currently, since it did not provide cross-reference such as monier.xml now.
/*            if(e.tagName() == "tail") {
               QDomNode pc = n.namedItem("pc");
               if(!pc.isNull()){
                    pc_string = pc.toElement().text();
                    result_string += "<br>" + markup_pagecolumn(pc_string);
               }
            }*/
        }
        n = n.nextSibling();
    }


    return result_string;
}


#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)
#else
Q_EXPORT_PLUGIN2(monieresdict, MonierESDict);
#endif
